# -*- coding: utf-8 -*-
# @Time    : 2024/6/13 16:57
# @Author  : yujiahao
# @File    : 22_modules_packages.py
# @description:Python中的模块和包


r"""

【模块是什么】：模块就是文件

 一个函数封装一个功能，你使用的软件可能就是由n多个函数组成的（先不考虑面向对象）。
 比如抖音这个软件，不可能将所有程序都写入一个文件，所以咱们应该将文件划分，这样其组织结构要好并且代码不冗余。
 假如分了10个文件，每个文件里面可能都有相同的功能（函数），怎么办？所以将这些相同的功能封装到一个文件中，
 那么这个存储着很多常用的功能的py文件，就是模块。 模块就是文件，存放一堆常用的函数，
 比如：我要在大草原上策马奔腾，应该怎么样？我应该骑马，你也要去浪，你是不是也要骑马。我们说一个函数就是一个功能，
 那么把一些常用的函数放在一个py文件中，这个文件就称之为模块，模块，就是一些列常用功能的集合体。

【为什么要使用模块】
    1、从文件级别组织程序，更方便管理, 随着程序的发展，功能越来越多，为了方便管理，我们通常将程序分成一个个的文件，这样做程序的结构更清晰，
        这时我们不仅仅可以把这些文件当做脚本去执行，还可以把他们当做模块来导入到其他的模块中，实现了功能的重复利用

    2、拿来主义，提升开发效率 同样的原理，我们也可以下载别人写好的模块然后导入到自己的项目中使用，这种拿来主义，
        可以极大地提升我们的开发效率，避免重复造轮子.

【常说的脚本是什么】

        如果你在终端上编写的代码运行完后,退出python解释器然后重新进入，那么你之前定义的函数或者变量都将丢失，
    因此我们通常将程序写到文件中以便永久保存下来，需要时就通过python test.py方式去执行，此时test.py被称为脚本script。
    所以，脚本就是一个python文件，比如你之前写的购物车，模拟登录系统的文件等等。



【模块的分类】

    Python语言中，模块分为三类。

        第一类：内置模块，也叫做标准库。此类模块就是python解释器给你提供的，比如我们之前见过的time模块,os模块。标准库的模块非常多（200多个，每个模块又有很多功能）。

        第二类：第三方模块，第三方库。一些python大神写的非常好用的模块，必须通过pip install 指令安装的模块，比如BeautfulSoup, Django,等等。大概有6000多个。

        第三类：自定义模块。我们自己在项目中定义的一些模块。

【import 使用】

    import语句是可以在程序中的任意位置使用的,且针对同一个模块import很多次,为了防止你重复导入，python的优化手段是：
    第一次导入后就将模块名加载到内存了，后续的import语句仅是对已经加载到内存中的模块对象增加了一次引用，
    不会重新执行模块内的语句，重复导入会直接引用内存中已经加载好的结果

【模块的命名】

    在 Python 中，模块的命名需要遵循一些基本的规则，以确保模块可以被正确导入和使用。以下是一些主要的命名规则和建议：

        模块名必须是有效的 Python 标识符：这意味着模块名只能包含字母、数字和下划线，且不能以数字开头。
        模块名通常使用小写字母：虽然这不是强制性的，但使用小写字母和下划线的命名约定是 Python 社区的惯例。
        基于这些规则，您需要将 22_moudules_1.py 重命名为一个有效的 Python 模块名，例如 modules_1.py。

【模块的别名】别名其实就是一个外号 ：import  模块名 as 外号

    1. 好处可以将很长的模块名改成很短,方便使用.
    2. 有利于代码的扩展和优化。


【导入多个模块】

    我们以后再开发过程中，免不了会在一个文件中，导入多个模块，推荐写法是一个一个导入。


        import os,sys,json   # 这样写可以但是不推荐

        推荐写法

        import os
        import sys
        import json
    【多行导入】：易于阅读 易于编辑 易于搜索 易于维护。

    Python：使用 import 语句来引用模块或包，可以使用 as 关键字指定别名。

    Java：使用 import 语句来引用包、类或接口，可以使用 .* 通配符导入包中的所有类和接口。

    Scala：使用 import 语句来引用对象、类、特质或包，可以使用 _ 通配符导入对象或包中的所有成员。

【from ... import ...】

    - 使用from...import...则是将spam中的名字直接导入到当前的名称空间中，所以在当前名称空间中，直接使用名字就可以了、无需加前缀,
    - 也支持 as 别名，
    - 也可以一行导入多个
    - 还可以使用from meet import * 把meet中所有的不是以下划线(_)开头的名字都导入到当前位置，不推荐使用
        > 大部分情况下我们的python程序不应该使用这种导入方式，因为*你不知道你导入什么名字，很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差，在交互式环境中导入时没有问题。
    - 可以使用all来控制*（用来发布新版本），在meet.py中新增一行
        __all__=['name','read1'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字

【from...import... 与import对比】

    好处：使用起来方便了
    坏处：容易与当前执行文件中的名字冲突

【模块循环导入问题】

    模块循环/嵌套导入抛出异常的根本原因是由于在python中模块被导入一次之后，就不会重新导入，只会在第一次导入时执行模块内代码

    在我们的项目中应该尽量避免出现循环/嵌套导入，如果出现多个模块都需要共享的数据，可以将共享的数据集中存放到某一个地方在程序出现了循环/嵌套导入后的异常分析、解决方法如下（了解，以后尽量避免）

【py文件的两种功能】
    编写好的一个python文件可以有两种用途：
    一：脚本，一个文件就是整个程序，用来被执行（比如你之前写的模拟博客园登录那个作业等）
    二：模块，文件中存放着一堆功能，用来被导入使用
    python为我们内置了全局变量__name__， 当文件被当做脚本执行时：__name__ 等于'__main__' 当文件被当做模块导入时：__name__等于模块名
    作用：用来控制.py文件在不同的应用场景下执行不同的逻辑（或者是在模块文件中测试代码）

        if __name__ == '__main__':

        Python：if __name__ == '__main__': 是一个用于确定模块执行路径的常用模式。

        # 假设有一个名为 math_operations.py 的 Python 模块，其中定义了一些数学操作函数

            # math_operations.py

                def add(x, y):
                    return x + y

                def subtract(x, y):
                    return x - y

                # 如果模块作为脚本直接执行，则执行以下代码
                if __name__ == '__main__':
                    print(add(10, 5))  # 输出：15
                    print(subtract(10, 5))  # 输出：5


        Java：使用 public static void main(String[] args) 方法作为程序的入口。

                // Java
                public class Main {
                    public static void main(String[] args) {
                        // 主程序代码
                    }
                }


        Scala：使用 def main(args: Array[String]): Unit 方法作为程序的入口。

                // Scala
                object Main {
                  def main(args: Array[String]): Unit = {
                    // 主程序代码
                  }
                }

【模块的搜索路径】

    Python中引用模块是按照一定的规则以及顺序去寻找的，
    这个查询顺序为：先从内存中已经加载的模块进行寻找找不到再从内置模块中寻找，内置模块如果也没有，最后去sys.path中路径包含的模块中寻找。
    它只会按照这个顺序从这些指定的地方去寻找，如果最终都没有找到，那么就会报错。

【内存中已经加载的模块->内置模块->sys.path路径中包含的模块】


【模块的查找顺序】

        在第一次导入某个模块时（比如meet），会先检查该模块是否已经被加载到内存中（当前执行文件的名称空间对应的内存），
        如果有则直接引用（ps：python解释器在启动时会自动加载一些模块到内存中，可以使用sys.modules查看）
        如果没有，解释器则会查找同名的内置模块
        如果还没有找到就从sys.path给出的目录列表中依次寻找meet.py文件。
    需要特别注意的是：我们自定义的模块名不应该与系统内置模块重名。


    在初始化后，Python 程序可以修改 sys.path，路径放到前面的优先于标准库被加载。

    示例用法：
        import sys
        sys.path.append('/a/b/c/d')
        sys.path.insert(0, '/x/y/z')  # 排在前的目录优先被搜索

    注意：
        - 搜索时按照 sys.path 中从左到右的顺序查找，位于前的路径优先被查找。
        - sys.path 中还可能包含 .zip 归档文件和 .egg 文件，Python 会把 .zip 归档文件当成一个目录去处理。

    制作归档文件：
        zip module.zip foo.py bar.py

    使用 .zip 归档文件：
        import sys
        sys.path.append('module.zip')
        import foo, bar

    使用 .zip 中目录结构的具体位置：
        sys.path.append('module.zip/lib/python')

    在 Windows 下处理路径：
        sys.path.insert(0, r'C:\Users\Administrator\PycharmProjects\a')  # 使用原始字符串表示路径

    关于 .egg 文件：
        - .egg 文件是由 setuptools 创建的包，用于分发第三方 Python 库和扩展。
        - .egg 文件实际上只是添加了额外元数据（如版本号、依赖项等）的 .zip 文件。

    需要强调的一点：
        - 只能从 .zip 文件中导入 .py、.pyc 等文件。使用 C 编写的共享库和扩展块无法直接从 .zip 文件中加载（此时 setuptools 等打包系统有时能提供一种规避方法）。
        - 从 .zip 中加载文件不会创建 .pyc 或 .pyo 文件，因此一定要事先创建它们，以避免加载模块时性能下降。





【Python中的包】

        只要文件夹下含有__init__.py文件就是一个包，这个文件可以是空的，但它的存在告诉 Python 这个目录应该被视为一个包，包能够管理多个模块
    用于组织和管理代码。这种组织方式不仅有助于代码的模块化和重用，还能避免命名冲突

【创建包的基本步骤】
        创建目录：创建一个新的目录作为包的根目录。
        添加 __init__.py 文件：在包的根目录中添加一个 __init__.py 文件。这个文件可以是空的，也可以包含包的初始化代码。
        添加模块：在包目录中添加其他模块（.py 文件）。
【导包】
包也可以嵌套，包里面可以有子包，我们在使用的时候可以通过  import  包名 或者 from 包 import 模块   都可以进行导包操作
Python、Java 和 Scala 中的包和模块对比



【__init__.py 的作用】


    1、标识包：__init__.py 文件的存在告诉 Python 这个目录应该被视为一个包。

    2、初始化代码：可以在 __init__.py 中编写包的初始化代码，例如设置包级别的变量或导入包中的模块。

        # mypackage/__init__.py

        import logging

        # 设置日志记录
        logging.basicConfig(level=logging.INFO)

        # 导入包中的模块
        from .module1 import foo
        from .module2 import bar

        # 包级别的变量
        __version__ = '1.0.0'


    3、简化导入：可以在 __init__.py 中导入包中【子包也可以】的模块，以便用户可以直接从包中导入这些模块。例如：

        # mypackage/__init__.py


        --all__ = ['foo', 'bar']
        或者
        from .module1 import foo
        from .module2 import bar


        这样用户可以直接导入模块中的函数：

        from mypackage import foo, bar

    4、动态导入模块

        有时你可能希望在运行时动态导入模块，这可以通过在 __init__.py 文件中使用 importlib 模块来实现：

        # mypackage/__init__.py

        import importlib

        def dynamic_import(module_name):
            return importlib.import_module(f'.{module_name}', package=__name__)

        # 动态导入 module1
        module1 = dynamic_import('module1')
        module1.foo()

    注意：在使用import有个注意点,python2中如果import包,没有__init__.py文件就会报错 python3 import没有__init__.py文件的包不会报错 from 包 import 包或者模块(在import后边不能在进行.操作)


【__all__ 的使用】

    在 __init__.py 文件中，可以使用 __all__ 列表来定义包的公共接口。这有助于控制从包中导入的内容。

            # mypackage/__init__.py

            __all__ = ['foo', 'bar']

            from .module1 import foo
            from .module2 import bar
        这样，当用户使用 from mypackage import * 时，只会导入 foo 和 bar：

            from mypackage import *

            foo()  # 输出: Hello from module1
            bar()  # 输出: Hello from module2

    总结
    __init__.py 文件在 Python 包中起着关键作用。它不仅标识目录为包，还可以包含包的初始化代码、导入包中的模块或子包，
    并通过 __all__ 控制包的公共接口。合理使用 __init__.py 文件可以提高包的可用性和可维护性。



1. Python

   1.1 模块

   1.1.1 定义：
       - 模块是一个包含 Python 代码的文件，文件名即模块名（不包含 .py 扩展名）。
       - 模块可以包含变量、函数、类以及可执行代码。

   1.1.2 导入：
       - 使用 import 关键字导入模块。
       - 可以导入整个模块或模块中的特定部分。

   1.1.3 命名空间：
       - 每个模块都有自己的命名空间，避免名称冲突。

   1.1.4 结构：
       - 模块是单个文件，可以组织相关的代码片段（函数、类等）。

   1.1.5 示例：

       mymodule.py
       ```
       def greet(name):
           return f"Hello, {name}!"

       def add(a, b):
           return a + b
       ```

       main.py
       ```
       import mymodule

       print(mymodule.greet("World"))  # 输出: Hello, World!
       print(mymodule.add(3, 4))       # 输出: 7
       ```

   1.2 包

   1.2.1 定义：
       - 包是一个包含多个模块的目录。目录中必须包含一个名为 __init__.py 的文件，该文件可以是空的，也可以包含包的初始化代码。

   1.2.2 导入：
       - 使用 import 关键字导入包中的模块或子包。
       - 可以导入整个包或包中的特定模块。

   1.2.3 命名空间：
       - 包提供了命名空间机制，避免模块名冲突。

   1.2.4 结构：
       - 包是一个目录，包含多个模块文件和一个 __init__.py 文件。

   1.2.5 示例：

       mypackage/__init__.py
       ```
       # 可以为空或包含初始化代码
       ```

       mypackage/module1.py
       ```
       def func1():
           return "Function 1"
       ```

       mypackage/module2.py
       ```
       def func2():
           return "Function 2"
       ```

       main.py
       ```
       import mypackage.module1
       import mypackage.module2

       print(mypackage.module1.func1())  # 输出: Function 1
       print(mypackage.module2.func2())  # 输出: Function 2
       ```

2. Java

   2.1 包

   2.1.1 定义：
       - 包是一个命名空间，用于组织类和接口。包名通常与目录结构对应。

   2.1.2 导入：
       - 使用 import 关键字导入包中的类或接口。
       - 可以导入整个包或包中的特定类或接口。

   2.1.3 命名空间：
       - 包提供了命名空间机制，避免类名冲突。

   2.1.4 结构：
       - 包是一个目录结构，包含多个类和接口文件。

   2.1.5 示例：

       目录结构
       ```
       com/example/myapp/
           MyApp.java
           util/
               MyUtil.java
       ```

       com/example/myapp/MyApp.java
       ```
       package com.example.myapp;

       import com.example.myapp.util.MyUtil;

       public class MyApp {
           public static void main(String[] args) {
               System.out.println(MyUtil.greet("World"));
           }
       }
       ```

       com/example/myapp/util/MyUtil.java
       ```
       package com.example.myapp.util;

       public class MyUtil {
           public static String greet(String name) {
               return "Hello, " + name;
           }
       }
       ```

3. Scala

   3.1 包

   3.1.1 定义：
       - 包是一个命名空间，用于组织类和对象。包名通常与目录结构对应。

   3.1.2 导入：
       - 使用 import 关键字导入包中的类或对象。
       - 可以导入整个包或包中的特定类或对象。

   3.1.3 命名空间：
       - 包提供了命名空间机制，避免类名冲突。

   3.1.4 结构：
       - 包是一个目录结构，包含多个类和对象文件。

   3.1.5 示例：

       目录结构
       ```
       com/example/myapp/
           MyApp.scala
           util/
               MyUtil.scala
       ```

       com/example/myapp/MyApp.scala
       ```
       package com.example.myapp

       import com.example.myapp.util.MyUtil

       object MyApp {
         def main(args: Array[String]): Unit = {
           println(MyUtil.greet("World"))
         }
       }
       ```

       com/example/myapp/util/MyUtil.scala
       ```
       package com.example.myapp.util

       object MyUtil {
         def greet(name: String): String = s"Hello, $name"
       }
       ```

4. 关键区别

   4.1 定义和结构：
       - Python 模块：是一个包含 Python 代码的单个文件。
       - Python 包：是一个包含多个模块的目录，必须包含一个 __init__.py 文件。
       - Java 和 Scala 包：是一个命名空间，通常对应一个目录结构，包含多个类和接口（Java）或类和对象（Scala）。

   4.2 导入机制：
       - Python 模块：使用 import 导入单个文件，可以导入整个模块或模块中的特定部分。
       - Python 包：使用 import 导入包中的模块或子包。
       - Java 和 Scala 包：使用 import 导入包中的类或接口（Java）或类和对象（Scala），可以导入整个包或包中的特定部分。

   4.3 命名空间管理：
       - Python 模块和包：每个模块和包都有自己的命名空间，避免名称冲突。
       - Java 和 Scala 包：包提供了命名空间机制，避免类名冲突。

   4.4 使用场景：
       - Python 模块：适用于组织相关的代码片段（函数、类等）在一个文件中。
       - Python 包：适用于组织多个相关的模块在一个目录结构中。
       - Java 和 Scala 包：适用于组织多个相关的类和接口（Java）或类和对象（Scala）在一个目录结构中。

5. 总结

   - Python 模块：是单个文件，用于组织相关的代码片段（函数、类等）。
   - Python 包：是目录结构和命名空间，用于组织多个相关的模块。
   - Java 和 Scala 包：是目录结构和命名空间，用于组织多个相关的类和接口（Java）或类和对象（Scala）。

   因此，Python 中的模块与 Java 和 Scala 中的包并不完全相同。Python 中的包更接近于 Java 和 Scala 中的包，而 Python 中的模块更类似于 Java 和 Scala 中的单个类文件。
"""

# todo  一、自定义模块

'''定义一个模块其实很简单就是写一个文件，里面写一些代码（变量，函数）即可，如 moudules_22_1.py '''

# 首先，我们在别的地方使用必须要先使用import关键字导进来，注意不能以数字开头，直接运行相当于运行 moudules_22_1.py
# 先注释掉
# import moudules_22_1


# from ... import ...

# 执行文件有与模块同名的变量或者函数名，会有覆盖效果。
# 当前我的name是这个
name = 'oldboy'

from moudules_22_1 import name

# 导入之后打印就变了，而且谁在靠后就打印谁的，会被覆盖
print(name)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# 2. 当前位置直接使用read1和read2就好了，执行时，仍然以moudules_22_1.py文件全局名称空间

# 测试一：导入的函数read1，执行时仍然回到moudules_22_1.py中寻找全局变量

from moudules_22_1 import read1

name = 'alex'
read1()

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# 测试二:导入的函数read2，执行时需要调用read1(),仍然回到 moudules_22_1.py中找
# read1()


from moudules_22_1 import read2


def read1():
    print('==========')


read2()

# 循环导入的问题

'''

    测试一：循环导入导致的异常

        文件结构：
        - m1.py:
            print('正在导入m1')
            from m2 import y
            x = 'm1'

        - m2.py:
            print('正在导入m2')
            from m1 import x
            y = 'm2'

        - run.py:
            import m1

        执行结果：
        正在导入m1
        正在导入m2
        Traceback (most recent call last):
          File "/python项目/run.py", line 1, in <module>
            import m1
          File "/python项目/m1.py", line 2, in <module>
            from m2 import y
          File "/python项目/m2.py", line 2, in <module>
            from m1 import x
        ImportError: cannot import name 'x'

        结果分析：
        1. 导入 m1：执行 run.py 时，首先导入 m1，打印 "正在导入m1"。
        2. 导入 m2：在 m1 中执行 from m2 import y，开始导入 m2，打印 "正在导入m2"。
        3. 再次导入 m1：在 m2 中执行 from m1 import x，由于 m1 尚未完全导入完成，x 还未定义，所以报错。

    测试二：直接执行 m1.py 导致的异常

        执行结果：
        正在导入m1
        正在导入m2
        Traceback (most recent call last):
        正在导入m1
          File "/python项目/m1.py", line 2, in <module>
            from m2 import y
          File "/python项目/m2.py", line 2, in <module>
            from m1 import x
          File "/python项目/m1.py", line 2, in <module>
            from m2 import y
        ImportError: cannot import name 'y'

        结果分析：
        1. 执行 m1.py：直接执行 m1.py，打印 "正在导入m1"。
        2. 导入 m2：在 m1 中执行 from m2 import y，开始导入 m2，打印 "正在导入m2"。
        3. 再次导入 m1：在 m2 中执行 from m1 import x，由于 m1 尚未完全导入完成，再次执行 m1，打印 "正在导入m1"。
        4. 循环导入：再次执行 from m2 import y，由于 m2 尚未完全导入完成，y 还未定义，所以报错。

    解决方法：

        方法一：导入语句放到最后
        - m1.py:
            print('正在导入m1')
            x = 'm1'
            from m2 import y

        - m2.py:
            print('正在导入m2')
            y = 'm2'
            from m1 import x

        这避免了在导入过程中访问尚未定义的变量，因为变量已经在导入语句之前定义好了。

    方法二：导入语句放到函数中
        - m1.py:
            print('正在导入m1')

            def f1():
                from m2 import y
                print(x, y)

            x = 'm1'

        - m2.py:
            print('正在导入m2')

            def f2():
                from m1 import x
                print(x, y)

            y = 'm2'

        - run.py:
            import m1

            m1.f1()

    解释：
        1. 延迟导入：将导入语句放在函数内部，使得导入只在函数被调用时发生，而不是在模块加载时发生，避免了循环导入问题。
        2. 模块加载顺序：当 run.py 导入 m1 时，m1 和 m2 都会被加载，但不会立即执行内部的导入语句。只有在调用 m1.f1() 时，才会执行 from m2 import y，此时 m2 已经完全加载，y 已经定义。

    通过这两种方法，可以有效避免循环导入问题，使代码更加健壮和易于维护。

'''
